/* -----------------------------------------------------------------------------
 * PROJECT    : lwf-jaxb
 * FILE       : FractionGenerator.java
 *
 * HISTORY:
 * Date          Author                        Description
 * ----------------------------------------------------------------------------- 
 * Mar 10, 2011  mycodetalks			       Created
 */
package net.lwf.jaxb.util.maths.fraction;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import net.lwf.jaxb.entity.Fraction;
import net.lwf.jaxb.service.maths.fraction.FractionDataService;
import net.lwf.jaxb.stubs.maths.fraction.Challenge;
import net.lwf.jaxb.stubs.maths.fraction.Data;
import net.lwf.jaxb.stubs.maths.fraction.Datarows;
import net.lwf.jaxb.stubs.maths.fraction.Denominator;
import net.lwf.jaxb.stubs.maths.fraction.Numerator;
import net.lwf.jaxb.stubs.maths.fraction.OperationType;
import net.lwf.jaxb.stubs.maths.fraction.Option;
import net.lwf.number.util.RandomNumberGenerator;

public class FractionGenerator {
	
	private int required;
	private Numerator numerator;
	private Denominator denominator;
	

	public FractionGenerator(String _grade, String operation) {
		FractionDataService service = new FractionDataService();
		Data fractiondata = service.getFractionData(_grade, operation);
		this.numerator = fractiondata.getNumerator();
		this.denominator = fractiondata.getDenominator();
	}
	
	public FractionGenerator(String _grade, String operation, String _operationtype, String _challenge) {
		FractionDataService service = new FractionDataService();
		Data fractiondata = service.getFractionData(_grade, operation);
		this.setNumerDenom(operation, _operationtype, _challenge, fractiondata);
	}
	
	private void setNumerDenom(String operation, String _operationtype, String _challenge, Data fractiondata) {
		List<OperationType> operationtypes = fractiondata.getOperationType();
		for(OperationType operationtype: operationtypes) {
			if(operationtype.getName().equalsIgnoreCase(_operationtype)) {
				List<Challenge> challenges = operationtype.getChallenge();
				for(Challenge challenge: challenges) {
					if(challenge.getName().equalsIgnoreCase(_challenge)) {
						List<Option> options = challenge.getOption();
						int randomoption = RandomNumberGenerator.generate(1, options.size());
						Option option = options.get(randomoption-1);
						Datarows datarows = option.getDatarows();
						required = RandomNumberGenerator.generate(datarows.getFrom(), datarows.getTo());
						this.numerator = option.getNumerator();
						this.denominator = option.getDenominator();
					}
				}
				return;
			}
		}

	}
	/**
	 * Generates a fraction based on the grade level. Input argument reduce
	 * determines whether the generated fraction to be reduced or not. 
	 * Example, 2/4 would be reduced to 1/2.
	 * 
	 * @param reduce
	 * 
	 * @return
	 */
	public Fraction generate(boolean reduce) {

		Fraction fraction = this.generate();
		if (!reduce) {
			return fraction;
		}
		return FractionUtil.reduce(fraction);
	}
	
	/**
	 * Generates multiple fraction based on the grade level. The count (multiple fractions)
	 * depends on the grade level mainly used for arithmatic operations.
	 * Input orgument reduce determines whether the generated fraction to be reduced or not. 
	 * Example, 2/4 would be reduced to 1/2.
	 * 
	 * @param reduce
	 * 
	 * @return
	 */
/*	public List<Fraction> generateMultiple(boolean reduce, int required) {
		Map<String, String> uniquechecker = new TreeMap<String, String>();
		List<Fraction> fractions = new ArrayList<Fraction>();
		
		Fraction fraction = null;
		String key = null;
		boolean iterate = true;
		while(iterate) {
			fraction = generate(reduce);
			key = fraction.toString();
			if(uniquechecker.size() != required) {
				if(!uniquechecker.containsKey(key)) {
					uniquechecker.put(key, key);
					fractions.add(fraction);
				}
			}
			else {
				iterate = false;
			}
		}
		return fractions;
	}*/
	
	public Map<String, String> generateMultiple(boolean reduce, int required) {
		Map<String, String> uniquechecker = new TreeMap<String, String>();
		
		Fraction fraction = null;
		String key = null;
		boolean iterate = true;
		while(iterate) {
			fraction = generate(reduce);
			key = fraction.toString();
			if(uniquechecker.size() != required) {
				if(!uniquechecker.containsKey(key)) {
					uniquechecker.put(key, key);
				}
			}
			else {
				iterate = false;
			}
		}
		return uniquechecker;
	}
	
	public List<Fraction> generateArithmaticFractions(boolean reduce) {
		List<Fraction> fractions = new ArrayList<Fraction>();
		int gen_denominator = this.generateDenominator(this.denominator);
		//int gen_denominator = 2;
		for (int i = 0; i < required; i++) {
			int gen_numerator = 0;
			Fraction fraction = null;
			if(this.denominator.isEqualDenominator()) {
				gen_numerator =reGenerateNumerator(gen_denominator);
			}
			else {
				gen_denominator = this.generateDenominator(this.denominator);
				gen_numerator = this.reGenerateNumerator(gen_denominator);
			}
			fraction = new Fraction(gen_numerator, gen_denominator);
			if(reduce) {
				fractions.add(FractionUtil.reduce(fraction));	
			}
			else {
				fractions.add(fraction);	
			}
			
		}
		return fractions;
	}
	
	private int reGenerateNumerator(int gen_denominator) {
		int gen_numerator = this.generateNumerator(this.numerator);
		if(this.numerator.isLessThanDenominator()) {
			while(gen_numerator >= gen_denominator) {
				gen_numerator = this.generateNumerator(this.numerator);
			}				
		}
		return gen_numerator;
	}
	
	public Fraction generateMixed() {
		Fraction fraction = this.generate();
		while(!fraction.isMixed()) {
			fraction = this.generate();
		}
		return fraction;
	}
	
	/**
	 * Generates Equal denominator fractions.
	 * 
	 * @param required Required number of fractions to be generated
	 * @param reduce   Reduce the fraction or not.
	 * 
	 * @return
	 */
	public List<Fraction> generateEqualDenominatorFraction(int required, boolean reduce) {
		List<Fraction> fractions = new ArrayList<Fraction>();
		int gen_numerator = 0;
		int gen_denominator = 0;
		Fraction fraction = null;
		
		for(int i=0; i<required; i++) {
			if(i == 0) {
				gen_numerator = this.generateNumerator(this.numerator);
				gen_denominator = this.generateDenominator(this.denominator);
			}
			else {
				gen_numerator = this.generateNumerator(this.numerator);
				while(gen_numerator > gen_denominator ) {
					gen_numerator = this.generateNumerator(this.numerator);
				}
			}
			fraction = new Fraction(gen_numerator, gen_denominator);
			if(reduce) {
				fractions.add(FractionUtil.reduce(fraction));
			}
			else {
				fractions.add(fraction);
			}
		}
		
		return fractions;
	}
	
	/**
	 * Generates the fraction. 
	 * 
	 * @return
	 */
	private Fraction generate() {
		int gen_numerator = this.generateNumerator(this.numerator);
		int gen_denominator = this.generateDenominator(this.denominator);
		
		while(this.numerator.isLessThanDenominator() && gen_numerator >= gen_denominator) {
			gen_numerator = this.generateNumerator(this.numerator);
			gen_denominator = this.generateDenominator(this.denominator);
		}
		
		
		return new Fraction(gen_numerator, gen_denominator);
	}

	/**
	 * Generates the number for the given input criteria.
	 * 
	 * @param numdenator
	 * 
	 * @return
	 */
	
	private int generateNumerator(Numerator numerator) {
		return this.generateNumber(this.numerator.getFrom(), this.numerator.getTo(), this.numerator.isIgnoreOddEven(), this.numerator.isEvenOnly());
	}
	
	private int generateDenominator(Denominator denominator) {
		return this.generateNumber(this.denominator.getFrom(), this.denominator.getTo(), this.denominator.isIgnoreOddEven(), this.denominator.isEvenOnly());
	}
	
	private int generateNumber(int from, int to, boolean ignoreOddEven, boolean evenOnly) {
		int gen_number = RandomNumberGenerator.generate(from, to);
		if(ignoreOddEven) {
			return gen_number;
		}
		if(evenOnly) {
			while(gen_number%2 != 0) {
				gen_number = RandomNumberGenerator.generate(from, to);
			}
		}
		else {
			while(gen_number%2 != 1) {
				gen_number = RandomNumberGenerator.generate(from, to);	
			}
						
		}
		return gen_number;
	}

	
}
